import { parsePort, type Gpio } from "../gpio";

export const INPUT_AIN = 0b00;
export const INPUT_FLOAT = 0b01;
export const INPUT_PUPD = 0b10;
export const INPUT_UP = 0b10;
export const INPUT_DOWN = 0b11;

export const OUTPUT_PP = 0b00;
export const OUTPUT_OD = 0b01;
export const OUTPUT_AF_PP = 0b10;
export const OUTPUT_AF_OD = 0b11;

export const MODE_IN = 0b00;
export const MODE_OUT_10MHZ = 0b01;
export const MODE_OUT_2MHZ = 0b10;
export const MODE_OUT_50MHZ = 0b11;

export interface GpioCrCnf {
    value: number,
    name: string,
}

export interface GpioCrMode {
    value: number,
    name: string,
}

export const GPIO_MODES: GpioCrMode[] = [
    {
        value: MODE_IN,
        name: '输入',
    }, {
        value: MODE_OUT_10MHZ,
        name: '输出10MHz',
    }, {
        value: MODE_OUT_2MHZ,
        name: '输出2MHz',
    }, {
        value: MODE_OUT_50MHZ,
        name: '输出50MHz',
    }
];

export const GPIO_INPUT_CNFS: GpioCrCnf[] = [
    {
        value: INPUT_AIN,
        name: '模拟输入',
    }, {
        value: INPUT_FLOAT,
        name: '浮空输入',
    }, {
        value: INPUT_UP,
        name: '上拉输入',
    }, {
        value: INPUT_DOWN,
        name: "下拉输入",
    }
];

export const GPIO_OUTPUT_CNFS: GpioCrCnf[] = [{
    value: OUTPUT_PP,
    name: '推挽输出',
}, {
    value: OUTPUT_OD,
    name: '开漏输出',
}, {
    value: OUTPUT_AF_PP,
    name: '复用推挽',
}, {
    value: OUTPUT_AF_OD,
    name: '复用开漏'
}];

export interface GpioCr {
    cnf: GpioCrCnf,
    mode: GpioCrMode,
}

export interface PinConfig {
    name: string,
    cfg: GpioCr,
    id: symbol,
}

export function newPinConfig(): PinConfig {
    return {
        name: 'PA0',
        cfg: {
            mode: GPIO_MODES[0],
            cnf: GPIO_INPUT_CNFS[0],
        },
        id: Symbol(),
    }
}

interface GpioReg {
    cr: number,
    bsrr: number,
    brr: number,
}

export interface GpioConfig {
    gpio: Gpio,
    reg: GpioReg,
}

function calcGpioState(info: GpioCr): GpioReg {
    let cr = (info.cnf.value << 2) + info.mode.value;
    let bsrr = 0;
    let brr = 0;
    
    if (info.mode.value == MODE_IN) {
        switch (info.cnf.value) {
            case INPUT_DOWN: brr = 1; cr = (INPUT_PUPD << 2) + MODE_IN; break;
            case INPUT_UP: bsrr = 1; cr = (INPUT_PUPD << 2) + MODE_IN; break;
        }
    }
    return {
        cr,
        bsrr,
        brr
    }
}

function groupByPort(cfgs: GpioConfig[]): Map<string, GpioConfig[]> {
    const map = new Map<string, GpioConfig[]>();
    for (const cfg of cfgs) {
        let gs = map.get(cfg.gpio.port);
        if (gs == null) {
            gs = []
            map.set(cfg.gpio.port, gs);
        }
        gs.push(cfg);
    }
    return map;
}

function convert(pinCnfs: PinConfig[]) {
    const rets: GpioConfig[] = [];
    for (const pin of pinCnfs) {
        const state = calcGpioState(pin.cfg);
        const gpioPin = parsePort(pin.name);
        rets.push({
            gpio: gpioPin,
            reg: state,
        });
    }
    return rets;
}

export function calc(pinCnfs: PinConfig[]) {
    const gpios = convert(pinCnfs);
    const map = groupByPort(gpios);
    const gpioConfs: GpioRegConfig[] = [];
    for (const [port, sub_gpios] of map) {
        const conf: GpioRegConfig = {
            port,
            crl_mask: 0,
            crl: 0,
            crh_mask: 0,
            crh: 0,
            brr: 0,
            bsrr: 0,
        };
        for (const gpio of sub_gpios) {
            
            let offset = gpio.gpio.pin;
            if (offset >= 8) {
                offset -= 8;
                conf.crh_mask |= 0x0F << (4 * offset);
                conf.crh |= gpio.reg.cr << (4 * offset);
            } else {
                conf.crl_mask |= 0x0F << (4 * offset);
                conf.crl |= gpio.reg.cr << (4 * offset);
            }
            conf.brr |= gpio.reg.brr << gpio.gpio.pin;
            conf.bsrr |= gpio.reg.bsrr << gpio.gpio.pin;
        }
        gpioConfs.push(conf);
    }
    return gpioConfs;
}

export interface GpioRegConfig {
    port: string,
    crl_mask: number,
    crl: number,
    crh_mask: number,
    crh: number,
    bsrr: number,
    brr: number,
}


